﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace TestByWire.Test
{
	[TestClass]
	public class GivenTests
	{
		private IUnityContainer _container;

		[TestInitialize]
		public void Init()
		{
			_container = new UnityContainer();
			Given.Reset(_container);
		}

		[TestMethod]
		public void That_ShouldRecordVoidStubbingCorrect()
		{
			var stub = MockRepository.GenerateStub<IDbAccess>();
			_container.RegisterInstance(stub);

			Given.That<IDbAccess>(x => x.StoreNewSalary("foo", 123));

			var recordings = GetRecordings();

			Assert.AreEqual(1, recordings.Count);
			var invocation = recordings[0];
			var inspector = new InvocationInspector(invocation);

			Assert.IsNull(inspector.ReturnValue);
			Assert.AreEqual(-1, inspector.RepeatCount);
			Assert.IsNull(inspector.ExceptionValue);
			Assert.AreEqual(null, inspector.Name);

		}

		[TestMethod]
		public void That_ShouldRecordNamedStubbingCorrect()
		{
			var stub = MockRepository.GenerateStub<IDbAccess>();
			_container.RegisterInstance("stub", stub);

			Given.That<IDbAccess>("stub", x => x.StoreNewSalary("foo", 123));

			var recordings = GetRecordings();

			Assert.AreEqual(1, recordings.Count);
			var invocation = recordings[0];
			var inspector = new InvocationInspector(invocation);

			Assert.IsNull(inspector.ReturnValue);
			Assert.AreEqual(-1, inspector.RepeatCount);
			Assert.IsNull(inspector.ExceptionValue);
			Assert.AreEqual("stub", inspector.Name);

		}

		[TestMethod]
		public void That_ShouldTrimListCorrect_WhenDefaultIsUnlimited()
		{
			var stub = MockRepository.GenerateStub<IDbAccess>();
			_container.RegisterInstance(stub);

			Given.That<IDbAccess>(x => x.GetCurrentSalary("foo")).Returns(23);
			Given.That<IDbAccess>(x => x.GetCurrentSalary("foo")).Returns(33);

			var recordings = GetRecordings();

			Assert.AreEqual(2, recordings.Count);
			var inspector = new InvocationInspector(recordings[0]);
			var inspector2 = new InvocationInspector(recordings[1]);

			Assert.AreEqual(23, inspector.ReturnValue);
			Assert.AreEqual(1, inspector.RepeatCount);
			Assert.IsNull(inspector.ExceptionValue);

			Assert.AreEqual(33, inspector2.ReturnValue);
			Assert.AreEqual(-1, inspector2.RepeatCount);
			Assert.IsNull(inspector2.ExceptionValue);
		}

		[TestMethod]
		public void That_ShouldTrimListCorrect_WhenWhenRepeatIsUserSpecified()
		{
			var stub = MockRepository.GenerateStub<IDbAccess>();
			_container.RegisterInstance(stub);

			Given.That<IDbAccess>(x => x.GetCurrentSalary("foo")).Returns(23).Repeat(2);
			Given.That<IDbAccess>(x => x.GetCurrentSalary("foo")).Returns(33);

			var recordings = GetRecordings();

			Assert.AreEqual(2, recordings.Count);
			var inspector = new InvocationInspector(recordings[0]);
			var inspector2 = new InvocationInspector(recordings[1]);

			Assert.AreEqual(23, inspector.ReturnValue);
			Assert.AreEqual(2, inspector.RepeatCount);
			Assert.IsNull(inspector.ExceptionValue);

			Assert.AreEqual(33, inspector2.ReturnValue);
			Assert.AreEqual(-1, inspector2.RepeatCount);
			Assert.IsNull(inspector2.ExceptionValue);
		}

		[TestMethod]
		public void That_ShouldRecordThrowsStubbingCorrect()
		{
			var stub = MockRepository.GenerateStub<IDbAccess>();
			_container.RegisterInstance(stub);

			var expectedException = new Exception("Blah");
			Given.That<IDbAccess>(x => x.StoreNewSalary("foo", 123)).Throws(expectedException);

			var recordings = GetRecordings();

			Assert.AreEqual(1, recordings.Count);
			var invocation = recordings[0];
			var inspector = new InvocationInspector(invocation);

			Assert.IsNull(inspector.ReturnValue);
			Assert.AreEqual(-1, inspector.RepeatCount);
			Assert.AreEqual(expectedException, inspector.ExceptionValue);
		}

		[TestMethod]
		public void That_ShouldRecordReturnStubbingsCorrect()
		{
			var stub = MockRepository.GenerateStub<IDbAccess>();
			_container.RegisterInstance(stub);

			Given.That<IDbAccess>(x => x.GetCurrentSalary("foo")).Returns(23).Repeat(3);

			var recordings = GetRecordings();

			Assert.AreEqual(1, recordings.Count);
			var invocation = recordings[0];
			var inspector = new InvocationInspector(invocation);

			Assert.AreEqual(23, inspector.ReturnValue);
			Assert.AreEqual(3, inspector.RepeatCount);
			Assert.IsNull(inspector.ExceptionValue);
		}

		[TestMethod]
		public void POC()
		{
			var expectedException = new Exception();

			var stub = MockRepository.GenerateStub<IDbAccess>();
			_container.RegisterInstance(stub);

			Given.That<IDbAccess>(x => x.GetCurrentSalary("Foo")).Returns(23);
			Given.That<IDbAccess>(x => x.GetCurrentSalary("Foo")).Returns(30).Repeat(2);
			Given.That<IDbAccess>(x => x.GetCurrentSalary("Foo")).Returns(40);

			Given.That<IDbAccess>(x => x.StoreNewSalary("Bar", 22));
			Given.That<IDbAccess>(x => x.StoreNewSalary("Bar", 22)).Throws(expectedException);

			Given.Transfer();

			Assert.AreEqual(23, stub.GetCurrentSalary("Foo"));
			Assert.AreEqual(30, stub.GetCurrentSalary("Foo"));
			Assert.AreEqual(30, stub.GetCurrentSalary("Foo"));
			for (int i = 0; i < 5; i++)
				Assert.AreEqual(40, stub.GetCurrentSalary("Foo"));

			stub.StoreNewSalary("Bar", 22);
			try
			{
				stub.StoreNewSalary("Bar", 22);
				Assert.Fail();
			}
			catch (Exception e)
			{
				Assert.AreEqual(expectedException, e);
			}
		}

		[TestMethod]
		public void FakeMustRegisterInteractionOnDynamicDeclarations()
		{
			DynamicDeclarations.Reset();

			Given.Fake(new Fakes.Calculator());

			Assert.AreEqual(1, DynamicDeclarations.GetDynamicInteractions().Length);
		}

		[TestMethod]
		public void FakeMustRegisterFakeInteraction()
		{
			DynamicDeclarations.Reset();

			Given.Fake(new Fakes.Calculator());

			Assert.AreEqual(DynamicDeclarations.GetDynamicInteractions()[0].RegistrationType, typeof(Fakes.Calculator));
		}

		[TestMethod]
		public void FakeMustThrowIfFakeInstanceIsNull()
		{
			DynamicDeclarations.Reset();

			var exception = AssertEx.Throws<ArgumentNullException>(() => Given.Fake<Fakes.Calculator>(null));

			Assert.AreEqual("fakeInstance", exception.ParamName);
		}

		[TestMethod]
		public void Fake_ShouldReplaceExistingContainerInteraction_WhenGivenAndAFakesSameInterface()
		{
			DynamicDeclarations.Reset();
			Scenario.Is(() => A.Fake<Fakes.ICalculator>(new Fakes.RealCalculator()));

			Given.Fake<Fakes.ICalculator>(new Fakes.FakeCalculator());

			Assert.AreEqual(1, DynamicDeclarations.GetDynamicInteractions().Length);
		}

		[TestMethod]
		public void FakeLateBound_ShouldThrow_WhenFieldNameIsNull()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => Given.FakeLateBound(null, 10));

			Assert.AreEqual("fieldName", exception.ParamName);
		}

		[TestMethod]
		public void FakeLateBound_ShouldThrow_WhenFieldNameIsEmptyString()
		{
			var exception = AssertEx.Throws<ArgumentNullException>(() => Given.FakeLateBound(string.Empty, 10));

			Assert.AreEqual("fieldName", exception.ParamName);
		}

		[TestMethod]
		public void FakeLateBound_ShouldThrow_IfScenarioIsRecording()
		{
			var exception = AssertEx.Throws<InvalidContextException>(() => Scenario.Is(() => Given.FakeLateBound<int>("_someField", 30)));

			Assert.AreEqual("Given statements cannot be part of external scenario", exception.Message);
		}

		[TestMethod]
		public void FakeLateBound_ShouldReplaceScenarioFake_WhenFieldNameAreSame()
		{
			DynamicDeclarations.Reset();
			Scenario.Is(() => A.FakeLateBound("_someFieldName", 10));

			Given.FakeLateBound("_someFieldName", 10);

			Assert.AreEqual(1, DynamicDeclarations.GetDynamicInteractions().Length);
		}

		[TestMethod]
		public void FakeLateBound_ShouldReplaceScenarioFakeWithCorrectType_WhenFieldNameAreSame()
		{
			DynamicDeclarations.Reset();
			Scenario.Is(() => A.FakeLateBound("_someFieldName", 10));

			Given.FakeLateBound("_someFieldName", 10);

			Assert.AreEqual(typeof(UseFakeLateBoundInteraction), DynamicDeclarations.GetDynamicInteractions()[0].GetType());
		}

		[TestMethod]
		public void FakeLateBound_ShouldAddCorrectContainerInteraction_WhenSpecifiyingLateBound()
		{
			DynamicDeclarations.Reset();

			Given.FakeLateBound<Fakes.ICalculator>("fieldName", new Fakes.FakeCalculator());

			Assert.AreEqual(typeof(UseFakeLateBoundInteraction), DynamicDeclarations.GetDynamicInteractions()[0].GetType());
		}

		private IList<IInvocationUpdater> GetRecordings()
		{
			FieldInfo fieldInfo = typeof(Recorder).GetField("_recordings", BindingFlags.NonPublic | BindingFlags.Static);
			return (IList<IInvocationUpdater>)fieldInfo.GetValue(null);
		}

	}

	internal class InvocationInspector
	{
		private object _returnValue;
		private int _repeatCount;
		private Exception _exceptionValue;
		private string _name;

		public InvocationInspector(IInvocationUpdater invocation)
		{
			Type type = invocation.GetType();
			BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;

			FieldInfo returnField = type.GetField("_returnValue", flags);
			FieldInfo repeatField = type.GetField("_repeatCount", flags);
			FieldInfo exceptionField = type.GetField("_exceptionToThrow", flags);
			FieldInfo nameField = type.GetField("_name", flags);

			_returnValue = returnField.GetValue(invocation);
			_repeatCount = (int)repeatField.GetValue(invocation);
			_exceptionValue = (Exception)exceptionField.GetValue(invocation);
			_name = (string)nameField.GetValue(invocation);
		}

		public Object ReturnValue
		{
			get
			{
				return _returnValue;
			}
		}

		public int RepeatCount
		{
			get
			{
				return _repeatCount;
			}
		}

		public Exception ExceptionValue
		{
			get
			{
				return _exceptionValue;
			}
		}

		public string Name
		{
			get
			{
				return _name;
			}
		}
	}

	public interface IDbAccess
	{
		int GetCurrentSalary(string userName);
		void StoreNewSalary(string userName, int newSalary);
		UserInfo GetUser();
		string ConnectionString { get; }
		string ProviderName { get; set; }
	}
}
